#include "cpu/helper.h"
#include "cpu/decode/modrm.h"

#include "all-instr.h"

typedef int (*helper_fun)(swaddr_t);
static make_helper(_2byte_esc);

#define make_group(name, item0, item1, item2, item3, item4, item5, item6, item7) \
    static helper_fun concat(opcode_table_, name)[8] = {                         \
        /* 0x00 */ item0, item1, item2, item3,                                   \
        /* 0x04 */ item4, item5, item6, item7};                                  \
    static make_helper(name)                                                     \
    {                                                                            \
        ModR_M m;                                                                \
        m.val = instr_fetch(eip + 1, 1);                                         \
        return concat(opcode_table_, name)[m.opcode](eip);                       \
    }

/* 0x80 */
make_group(group1_b,
           inv, inv, inv, inv,
           and_i2rm_b, inv, inv, cmp_i2rm_b)

    /* 0x81 */
    make_group(group1_v,
               add_i2rm_v, inv, inv, inv,
               and_i2rm_v, sub_i2rm_v, inv, cmp_i2rm_v)

    /* 0x83 */
    make_group(group1_sx_v,
               add_si2rm_v, or_si2rm_v, inv, inv,
               and_si2rm_v, sub_si2rm_v, inv, cmp_si2rm_v)

    /* 0xc0 */
    make_group(group2_i_b,
               inv, inv, inv, inv,
               shl_rm_imm_b, shr_rm_imm_v, inv, sar_rm_imm_b)

    /* 0xc1 */
    make_group(group2_i_v,
               inv, inv, inv, inv,
               shl_rm_imm_v, shr_rm_imm_v, inv, sar_rm_imm_v)

    /* 0xd0 */
    make_group(group2_1_b,
               inv, inv, inv, inv,
               shl_rm_1_b, inv, inv, sar_rm_1_b)

    /* 0xd1 */
    make_group(group2_1_v,
               inv, inv, inv, inv,
               shl_rm_1_v, inv, inv, sar_rm_1_v)

    /* 0xd2 */
    make_group(group2_cl_b,
               inv, inv, inv, inv,
               shl_rm_cl_b, inv, inv, sar_rm_cl_b)

    /* 0xd3 */
    make_group(group2_cl_v,
               inv, inv, inv, inv,
               shl_rm_cl_v, inv, inv, sar_rm_cl_v)

    /* 0xf6 */
    make_group(group3_b,
               test_i2rm_b, inv, inv, inv,
               inv, inv, inv, inv)

    /* 0xf7 */
    make_group(group3_v,
               test_i2rm_v, inv, not_rm_v, neg_rm_v,
               mul_rm_v, imul_rm2a_v, div_rm_v, idiv_rm_v)

    /* 0xfe */
    make_group(group4,
               inv, inv, inv, inv,
               inv, inv, inv, inv)

    /* 0xff */
    make_group(group5,
               inv, dec_rm_v, call_rm_v, call_rm_v,
               jmp_rm_l, inv, push_rm_v, inv)

        make_group(group6,
                   inv, inv, inv, inv,
                   inv, inv, inv, inv)

            make_group(group7,
                       inv, inv, inv, inv,
                       inv, inv, inv, inv)

    /* TODO: Add more instructions!!! */

    helper_fun opcode_table[256] = {
        /* 0x00 */ add_r2rm_b, add_r2rm_v, add_rm2r_b, add_rm2r_v,
        /* 0x04 */ inv, add_i2a_v, inv, inv,
        /* 0x08 */ inv, or_r2rm_v, or_rm2r_b, inv,
        /* 0x0c */ or_i2a_b, inv, inv, _2byte_esc,
        /* 0x10 */ inv, adc_r2rm_v, inv, inv,
        /* 0x14 */ inv, inv, inv, inv,
        /* 0x18 */ inv, sbb_r2rm_v, inv, inv,
        /* 0x1c */ inv, inv, inv, inv,
        /* 0x20 */ and_r2rm_b, and_r2rm_v, and_rm2r_b, and_rm2r_v,
        /* 0x24 */ inv, and_i2a_v, inv, inv,
        /* 0x28 */ inv, sub_r2rm_v, inv, sub_rm2r_v,
        /* 0x2c */ inv, sub_i2a_v, inv, inv,
        /* 0x30 */ inv, xor_r2rm_v, inv, inv,
        /* 0x34 */ inv, inv, inv, inv,
        /* 0x38 */ cmp_r2rm_b, cmp_r2rm_v, cmp_rm2r_b, cmp_rm2r_v,
        /* 0x3c */ cmp_i2a_b, cmp_i2a_v, inv, inv,
        /* 0x40 */ inc_r_v, inc_r_v, inc_r_v, inc_r_v,
        /* 0x44 */ inv, inc_r_v, inc_r_v, inc_r_v,
        /* 0x48 */ dec_r_v, dec_r_v, dec_r_v, dec_r_v,
        /* 0x4c */ inv, dec_r_v, dec_r_v, dec_r_v,
        /* 0x50 */ push_r_v, push_r_v, push_r_v, push_r_v,
        /* 0x54 */ inv, push_r_v, push_r_v, push_r_v,
        /* 0x58 */ pop_r_v, pop_r_v, pop_r_v, pop_r_v,
        /* 0x5c */ inv, pop_r_v, pop_r_v, pop_r_v,
        /* 0x60 */ inv, inv, inv, inv,
        /* 0x64 */ inv, inv, operand_size, inv,
        /* 0x68 */ push_i_v, imul_i_rm2r_v, push_i_b, imul_si_rm2r_v,
        /* 0x6c */ inv, inv, inv, inv,
        /* 0x70 */ inv, inv, inv, inv,
        /* 0x74 */ je_i_b, jne_i_b, jbe_i_b, ja_i_b,
        /* 0x78 */ js_i_b, jns_i_b, inv, inv,
        /* 0x7c */ jl_i_b, jge_i_b, jle_i_b, jg_i_b,
        /* 0x80 */ group1_b, group1_v, inv, group1_sx_v,
        /* 0x84 */ test_r2rm_b, test_r2rm_v, inv, inv,
        /* 0x88 */ mov_r2rm_b, mov_r2rm_v, mov_rm2r_b, mov_rm2r_v,
        /* 0x8c */ inv, lea, inv, inv,
        /* 0x90 */ nop, inv, inv, inv,
        /* 0x94 */ inv, inv, inv, inv,
        /* 0x98 */ inv, cltd_v, inv, inv,
        /* 0x9c */ inv, inv, inv, inv,
        /* 0xa0 */ mov_moffs2a_b, mov_moffs2a_v, mov_a2moffs_b, mov_a2moffs_v,
        /* 0xa4 */ movs_b, movs_v, inv, inv,
        /* 0xa8 */ inv, inv, stos_b, stos_v,
        /* 0xac */ lods_n_b, lods_n_v, scas_b, inv,
        /* 0xb0 */ mov_i2r_b, mov_i2r_b, mov_i2r_b, mov_i2r_b,
        /* 0xb4 */ mov_i2r_b, mov_i2r_b, mov_i2r_b, mov_i2r_b,
        /* 0xb8 */ mov_i2r_v, mov_i2r_v, mov_i2r_v, mov_i2r_v,
        /* 0xbc */ mov_i2r_v, mov_i2r_v, mov_i2r_v, mov_i2r_v,
        /* 0xc0 */ group2_i_b, group2_i_v, ret_i_v, ret_n_v,
        /* 0xc4 */ inv, inv, mov_i2rm_b, mov_i2rm_v,
        /* 0xc8 */ inv, leave, inv, inv,
        /* 0xcc */ int3, inv, inv, inv,
        /* 0xd0 */ group2_1_b, group2_1_v, group2_cl_b, group2_cl_v,
        /* 0xd4 */ inv, inv, nemu_trap, inv,
        /* 0xd8 */ inv, inv, inv, inv,
        /* 0xdc */ inv, inv, inv, inv,
        /* 0xe0 */ inv, inv, inv, inv,
        /* 0xe4 */ inv, inv, inv, inv,
        /* 0xe8 */ call_i_v, jmp_si_l, inv, jmp_si_b,
        /* 0xec */ inv, inv, inv, inv,
        /* 0xf0 */ inv, inv, repnz, rep,
        /* 0xf4 */ inv, inv, group3_b, group3_v,
        /* 0xf8 */ inv, inv, inv, inv,
        /* 0xfc */ inv, inv, group4, group5};

helper_fun _2byte_opcode_table[256] = {
    /* 0x00 */ group6, group7, inv, inv,
    /* 0x04 */ inv, inv, inv, inv,
    /* 0x08 */ inv, inv, inv, inv,
    /* 0x0c */ inv, inv, inv, inv,
    /* 0x10 */ inv, inv, inv, inv,
    /* 0x14 */ inv, inv, inv, inv,
    /* 0x18 */ inv, inv, inv, inv,
    /* 0x1c */ inv, inv, inv, inv,
    /* 0x20 */ inv, inv, inv, inv,
    /* 0x24 */ inv, inv, inv, inv,
    /* 0x28 */ inv, inv, inv, inv,
    /* 0x2c */ inv, inv, inv, inv,
    /* 0x30 */ inv, inv, inv, inv,
    /* 0x34 */ inv, inv, inv, inv,
    /* 0x38 */ inv, inv, inv, inv,
    /* 0x3c */ inv, inv, inv, inv,
    /* 0x40 */ inv, inv, inv, inv,
    /* 0x44 */ inv, inv, inv, inv,
    /* 0x48 */ inv, inv, inv, inv,
    /* 0x4c */ inv, inv, inv, inv,
    /* 0x50 */ inv, inv, inv, inv,
    /* 0x54 */ inv, inv, inv, inv,
    /* 0x58 */ inv, inv, inv, inv,
    /* 0x5c */ inv, inv, inv, inv,
    /* 0x60 */ inv, inv, inv, inv,
    /* 0x64 */ inv, inv, inv, inv,
    /* 0x68 */ inv, inv, inv, inv,
    /* 0x6c */ inv, inv, inv, inv,
    /* 0x70 */ inv, inv, inv, inv,
    /* 0x74 */ inv, inv, inv, inv,
    /* 0x78 */ inv, inv, inv, inv,
    /* 0x7c */ inv, inv, inv, inv,
    /* 0x80 */ inv, inv, inv, inv,
    /* 0x84 */ je_i_v, jne_i_v, jbe_i_v, ja_i_v,
    /* 0x88 */ inv, inv, inv, inv,
    /* 0x8c */ jl_i_v, jge_i_v, jle_i_v, jg_i_v,
    /* 0x90 */ inv, inv, inv, inv,
    /* 0x94 */ inv, setne_rm_b, inv, inv,
    /* 0x98 */ inv, inv, inv, inv,
    /* 0x9c */ inv, inv, inv, inv,
    /* 0xa0 */ inv, inv, inv, inv,
    /* 0xa4 */ inv, inv, inv, inv,
    /* 0xa8 */ inv, inv, inv, inv,
    /* 0xac */ shrdi_v, inv, inv, imul_rm2r_v,
    /* 0xb0 */ inv, inv, inv, inv,
    /* 0xb4 */ inv, inv, movzb_v, movzw_l,
    /* 0xb8 */ inv, inv, inv, inv,
    /* 0xbc */ inv, inv, movsb_v, movsw_l,
    /* 0xc0 */ inv, inv, inv, inv,
    /* 0xc4 */ inv, inv, inv, inv,
    /* 0xc8 */ inv, inv, inv, inv,
    /* 0xcc */ inv, inv, inv, inv,
    /* 0xd0 */ inv, inv, inv, inv,
    /* 0xd4 */ inv, inv, inv, inv,
    /* 0xd8 */ inv, inv, inv, inv,
    /* 0xdc */ inv, inv, inv, inv,
    /* 0xe0 */ inv, inv, inv, inv,
    /* 0xe4 */ inv, inv, inv, inv,
    /* 0xe8 */ inv, inv, inv, inv,
    /* 0xec */ inv, inv, inv, inv,
    /* 0xf0 */ inv, inv, inv, inv,
    /* 0xf4 */ inv, inv, inv, inv,
    /* 0xf8 */ inv, inv, inv, inv,
    /* 0xfc */ inv, inv, inv, inv};

make_helper(exec)
{
    ops_decoded.opcode = instr_fetch(eip, 1);
    return opcode_table[ops_decoded.opcode](eip);
}

static make_helper(_2byte_esc)
{
    eip++;
    uint32_t opcode = instr_fetch(eip, 1);
    ops_decoded.opcode = opcode | 0x100;
    return _2byte_opcode_table[opcode](eip) + 1;
}
